package ngat.oss.simulation.metrics;


import ngat.oss.simulation.*;

import ngat.util.*;
import ngat.phase2.*;
import ngat.astrometry.*;
import ngat.util.logging.*;

import java.util.*;

public class OptimalAirmassPriorityUtilityCalculator implements UtilityCalculator {

    /** Default time step for elevation calculation.*/
    public static final long DEFAULT_TIME_STEP = 5*60*1000L;

    /** The site - needed for astrometrics calculations - do I need a SiteAstro object ?.*/
    private Site site;

    /** Timing constraints window calculator.*/
    private TimingConstraintWindowCalculator tcwc;

    /** Exec timing and feasibility model.*/
    private ExecutionTimingModel execModel;

    /** Timestep for elevation calculation (ms).*/
    private long timeStep;

    /** Logging.*/
    private LogProxy logger;

    /** Create an OptimalAirmassUtilityCalculator using the supplied models and time step.*/
    public OptimalAirmassPriorityUtilityCalculator(Site site,
						   TimingConstraintWindowCalculator tcwc,
						   ExecutionTimingModel execModel,
						   long timeStep) {
	this.site =  site;
	this.tcwc = tcwc;
	this.execModel = execModel;
	this.timeStep  = timeStep;

	Logger slogger = LogManager.getLogger("SIM");
	logger = new LogProxy("PXOAC", "", slogger);
    }

    /** Return the utility for the specified group at time under env.*/
    public double getUtility(Group group, long time, EnvironmentSnapshot env, ExecutionStatistics hist) {
	logger.method("getUtility").log(3, "G="+group.getName()+", t="+time+", env="+env+", h="+hist);

	// work out which window this is for the group using the TCWC
	TimeWindow window = tcwc.getWindow(group, hist, time);
	
	// Intersect with tonight - just use t+/-24H for now.
	window.start = Math.max(window.start, time-24*3600*1000L);
	window.end   = Math.min(window.end,   time+24*3600*1000L);

	// Find the target (mean) elevation at each instant in the window where
	// the group is actually executable. Use a small timestep.
	logger.log(3, "Start OH utility calculation for group "+group.getClass().getName()+" ("+group.getName()+
		   " at: Time="+ScheduleSimulator.sdf.format(new Date(time))+
		   " Curr Window runs from "+ScheduleSimulator.sdf.format(new Date(window.start))+
		   " to "+ScheduleSimulator.sdf.format(new Date(window.end)));
		   
	double minAirmass = 999.999;
	long   minT    = 0L;
	long t = window.start;
	// TEMP: Adjustable timestep...
	long tstep = (long) ((double)(window.end - window.start)/1000.0);
	int istep = 0;
	while (t < window.end) {
	    istep++;
	    if (execModel.canDo(group, t, env, hist)) {
		logger.log(3, "Step: "+istep+" at: "+ScheduleSimulator.sdf.format(new Date(t))+
			   " ok can exec, testing target mean elevation...");
		double airmass = getMeanAirmass(group, t);
		if (airmass < minAirmass) {
		    minAirmass = airmass;
		    minT = t;
		    logger.log(3, "Step: "+istep+" ok, elev > max sofar: "+Position.toDegrees(airmass, 3)+
			       " Max sofar now:"+Position.toDegrees(minAirmass,3)+" at "+ ScheduleSimulator.sdf.format(new Date(minT)));
		}
	    }
	    //t += timeStep;
	    t += tstep;
	}

	logger.log(3, "Completed test for max elev in window, maxElev= "+ Position.toDegrees(minAirmass,3));

	double tAirmass =  getMeanAirmass(group, time);
	// weve got the actual elev at t and the maximum - just need to combine them in some
	// clever way 

	double pp   = 0.0;

	switch (group.getPriority()) {
	case -2:
	    pp = 0.0;
	    break;
	case -1:
	    pp = 3.0;
	    break;
	case 0:
	    pp = 1.0;
	    break;
	default:
	    pp = (double)group.getPriority();
	}

	double exec = (double)execModel.getExecTime(group);
	
	return (minAirmass/tAirmass)*pp*exec; 
	// or (tElev - dome)/(maxElev - dome) ?  
	// or some exponential based on time difference, e.g. exp(-||maxT - t||/td ?

    }

    /** Calculate the mean elevation for any targets of the specified group at time.
     * A more sophisticated implementation might weight the target position according
     * to the amount of time exposing on it or the likes, this one just averages
     * the target elevations equally on a per observation basis 
     * with no account taken of sequencing etc.
     * This will produce the correct result for the vast majority of groups which
     * only have one target no matter how many observations or sequencing.
     */
    private double getMeanAirmass(Group group, long time) {

	double mairmass=  0.0;
	int nt = 0;
	Iterator iobs = group.listAllObservations();
	while (iobs.hasNext()) {
	    Observation obs = (Observation)iobs.next();
	    Source src = obs.getSource();
	    Position target = src.getPosition();
	    
	    double elev = target.getAltitude(time, site);
	    double airmass = 1.0/Math.cos(0.5*Math.PI - elev);
	    
	    logger.log(3, "Target: "+src.getClass().getName()+" ("+src.getName()+") "+
		       ScheduleSimulator.sdf.format(new Date(time))+", Airmass="+airmass);
	    
	    mairmass += airmass;
	    nt++;
	}

	double me = mairmass/(double)nt;
	logger.log(3, "Mean airmass from "+nt+" targets in group: "+me);

	return me;

    }
    
}
	
